`include "cpu_def.vh"

module mmu(
  input clk,
  input rst,

  // from pc
  input itlb0_flush,
  input itlb0_stall,

  // from if stage
  input [31:0] instr_vaddr,
  input instr_vaddr_valid,

  // to if stage
  output [31:0] instr_paddr      ,
  output        instr_paddr_valid,
  output        instr_tlb_refill ,
  output        instr_tlb_invalid,

  // from ex
  input dtlb0_flush,
  input dtlb0_stall,

  // from lsu
  input  [31:0] data_vaddr,
  input  data_vaddr_valid,
  input         is_store  ,

  // to lsu
  output [31:0] data_paddr       ,
  output        data_paddr_valid ,
  output        data_tlb_refill  ,
  output        data_tlb_invalid ,
  output        data_tlb_modified,

  // from wb (tlb instr)
  input        tlbwi_taken,
  input        tlbwr_taken,

  // to cache
  output        instr_uncache    ,
  output        data_uncache     ,

  // from cp0 (tlb instr)
  input [31:0] cp0_index_rdata   ,
  input [31:0] cp0_random_rdata  ,
  input [31:0] cp0_entrylo0_rdata,
  input [31:0] cp0_entrylo1_rdata,
  input [31:0] cp0_entryhi_rdata ,
  input [31:0] cp0_config0_rdata ,

  // to cp0 (tlb instr)
  output [31:0] cp0_index_wdata   ,
  output [31:0] cp0_entrylo0_wdata,
  output [31:0] cp0_entrylo1_wdata,
  output [31:0] cp0_entryhi_wdata 
);

  // tlb search port 0
  wire [                18:0] s0_vpn2;
  wire                        s0_odd_page;
  wire [                 7:0] s0_asid;

  wire                        s0_found;
  wire [$clog2(`TLB_NUM)-1:0] s0_index;
  wire                        s0_g;
  wire [                19:0] s0_pfn0;
  wire [                 2:0] s0_c0;
  wire                        s0_d0;
  wire                        s0_v0; 
  wire [                19:0] s0_pfn1;
  wire [                 2:0] s0_c1;
  wire                        s0_d1;
  wire                        s0_v1; 

  // tlb search port 1
  wire [                18:0] s1_vpn2;
  wire                        s1_odd_page;
  wire [                 7:0] s1_asid;

  wire                        s1_found;
  wire [$clog2(`TLB_NUM)-1:0] s1_index;
  wire                        s1_g;
  wire [                19:0] s1_pfn0;
  wire [                 2:0] s1_c0;
  wire                        s1_d0;
  wire                        s1_v0;
  wire [                19:0] s1_pfn1;
  wire [                 2:0] s1_c1;
  wire                        s1_d1;
  wire                        s1_v1;

  // tlb search port 2
  wire [                  18:0] s2_vpn2;
  wire [                   7:0] s2_asid;
  wire                          s2_found;
  wire [$clog2(`TLB_NUM) - 1:0] s2_index;

  // tlb write port
  wire                          we;
  wire [$clog2(`TLB_NUM) - 1:0] w_index;
  wire [                  18:0] w_vpn2;
  wire [                   7:0] w_asid;
  wire                          w_g;
  wire [                  19:0] w_pfn0;
  wire [                   2:0] w_c0;
  wire                          w_d0;
  wire                          w_v0;
  wire [                  19:0] w_pfn1;
  wire [                   2:0] w_c1;
  wire                          w_d1;
  wire                          w_v1;

  // tlb read port
  wire [$clog2(`TLB_NUM) - 1:0] r_index;
  wire [                  18:0] r_vpn2;
  wire [                   7:0] r_asid;
  wire                          r_g;
  wire [                  19:0] r_pfn0;
  wire [                   2:0] r_c0;
  wire                          r_d0;
  wire                          r_v0;
  wire [                  19:0] r_pfn1;
  wire [                   2:0] r_c1;
  wire                          r_d1;
  wire                          r_v1;

  // itlb0 search port
  wire [18:0] itlb0_s_vpn2;
  wire        itlb0_s_odd_page;
  wire [ 7:0] itlb0_s_asid;

  wire        itlb0_s_found;
  wire [19:0] itlb0_s_pfn;
  wire [ 2:0] itlb0_s_c;
  wire        itlb0_s_d;
  wire        itlb0_s_v; 

  // itlb0 write port
  wire        itlb0_we;
  wire [18:0] itlb0_w_vpn2;
  wire [ 7:0] itlb0_w_asid;
  wire        itlb0_w_g;
  wire [19:0] itlb0_w_pfn0;
  wire [ 2:0] itlb0_w_c0;
  wire        itlb0_w_d0;
  wire        itlb0_w_v0;
  wire [19:0] itlb0_w_pfn1;
  wire [ 2:0] itlb0_w_c1;
  wire        itlb0_w_d1;
  wire        itlb0_w_v1;

  // dtlb0 search port
  wire [18:0] dtlb0_s_vpn2;
  wire        dtlb0_s_odd_page;
  wire [ 7:0] dtlb0_s_asid;

  wire        dtlb0_s_found;
  wire [19:0] dtlb0_s_pfn;
  wire [ 2:0] dtlb0_s_c;
  wire        dtlb0_s_d;
  wire        dtlb0_s_v; 

  // dtlb0 write port
  wire        dtlb0_we;
  wire [18:0] dtlb0_w_vpn2;
  wire [ 7:0] dtlb0_w_asid;
  wire        dtlb0_w_g;
  wire [19:0] dtlb0_w_pfn0;
  wire [ 2:0] dtlb0_w_c0;
  wire        dtlb0_w_d0;
  wire        dtlb0_w_v0;
  wire [19:0] dtlb0_w_pfn1;
  wire [ 2:0] dtlb0_w_c1;
  wire        dtlb0_w_d1;
  wire        dtlb0_w_v1;

  assign s0_asid = cp0_entryhi_rdata[ 7: 0];
  
  assign s1_asid = cp0_entryhi_rdata[ 7: 0];

  assign s2_vpn2 = cp0_entryhi_rdata[31:13];
  assign s2_asid = cp0_entryhi_rdata[ 7: 0];

  assign itlb0_s_asid = cp0_entryhi_rdata[ 7: 0];

  assign dtlb0_s_asid = cp0_entryhi_rdata[ 7: 0];

  assign we      = tlbwi_taken || tlbwr_taken;
  assign w_index = 
    {$clog2(`TLB_NUM){tlbwi_taken}} & cp0_index_rdata [$clog2(`TLB_NUM) - 1:0] |
    {$clog2(`TLB_NUM){tlbwr_taken}} & cp0_random_rdata[$clog2(`TLB_NUM) - 1:0] ;
  assign w_vpn2  = cp0_entryhi_rdata[31:13];
  assign w_asid  = cp0_entryhi_rdata[ 7: 0];
  assign w_g     = cp0_entrylo0_rdata[   0] & cp0_entrylo1_rdata[   0];
  assign w_pfn0  = cp0_entrylo0_rdata[25:6];
  assign w_c0    = cp0_entrylo0_rdata[ 5:3];
  assign w_d0    = cp0_entrylo0_rdata[   2];
  assign w_v0    = cp0_entrylo0_rdata[   1];
  assign w_pfn1  = cp0_entrylo1_rdata[25:6];
  assign w_c1    = cp0_entrylo1_rdata[ 5:3];
  assign w_d1    = cp0_entrylo1_rdata[   2];
  assign w_v1    = cp0_entrylo1_rdata[   1];

  assign itlb0_we      = (instr_vaddr_valid && !itlb0_s_found && s0_found && !instr_paddr_valid) || (tlbwi_taken || tlbwr_taken);
  assign itlb0_w_vpn2  = (tlbwi_taken || tlbwr_taken) ? cp0_entryhi_rdata[31:13] : itlb0_s_vpn2;
  assign itlb0_w_asid  = (tlbwi_taken || tlbwr_taken) ? cp0_entryhi_rdata[ 7: 0] : itlb0_s_asid;
  assign itlb0_w_g     = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[   0] & cp0_entrylo1_rdata[   0] : s0_g;
  assign itlb0_w_pfn0  = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[25:6] : s0_pfn0;
  assign itlb0_w_c0    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[ 5:3] : s0_c0  ;
  assign itlb0_w_d0    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[   2] : s0_d0  ;
  assign itlb0_w_v0    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[   1] : s0_v0  ;
  assign itlb0_w_pfn1  = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[25:6] : s0_pfn1;
  assign itlb0_w_c1    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[ 5:3] : s0_c1  ;
  assign itlb0_w_d1    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[   2] : s0_d1  ;
  assign itlb0_w_v1    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[   1] : s0_v1  ;

  assign dtlb0_we      = (data_vaddr_valid && !dtlb0_s_found && s1_found && !data_paddr_valid) || (tlbwi_taken || tlbwr_taken);
  assign dtlb0_w_vpn2  = (tlbwi_taken || tlbwr_taken) ? cp0_entryhi_rdata[31:13] : dtlb0_s_vpn2;
  assign dtlb0_w_asid  = (tlbwi_taken || tlbwr_taken) ? cp0_entryhi_rdata[ 7: 0] : dtlb0_s_asid;
  assign dtlb0_w_g     = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[   0] & cp0_entrylo1_rdata[   0] : s1_g;
  assign dtlb0_w_pfn0  = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[25:6] : s1_pfn0;
  assign dtlb0_w_c0    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[ 5:3] : s1_c0  ;
  assign dtlb0_w_d0    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[   2] : s1_d0  ;
  assign dtlb0_w_v0    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo0_rdata[   1] : s1_v0  ;
  assign dtlb0_w_pfn1  = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[25:6] : s1_pfn1;
  assign dtlb0_w_c1    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[ 5:3] : s1_c1  ;
  assign dtlb0_w_d1    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[   2] : s1_d1  ;
  assign dtlb0_w_v1    = (tlbwi_taken || tlbwr_taken) ? cp0_entrylo1_rdata[   1] : s1_v1  ;

  assign r_index = cp0_index_rdata[$clog2(`TLB_NUM) - 1:0];
  assign cp0_index_wdata    = {~s2_found, 27'd0, s2_index};
  assign cp0_entrylo0_wdata = {6'd0, r_pfn0, r_c0, r_d0, r_v0, r_g};
  assign cp0_entrylo1_wdata = {6'd0, r_pfn1, r_c1, r_d1, r_v1, r_g};
  assign cp0_entryhi_wdata  = {r_vpn2, 5'd0, r_asid};

  iva2pa mycpu_iva2pa (
    .clk(clk),
    .rst(rst),
    .flush(itlb0_flush),
    .stall(itlb0_stall),

    .vaddr  (instr_vaddr  ),
    .vvalid (instr_vaddr_valid),

    .paddr  (instr_paddr  ),
    .pvalid (instr_paddr_valid),

    .cp0_config0(cp0_config0_rdata),
    .uncache    (instr_uncache    ),

    .tlb_found   (itlb0_s_found   ),
    .tlb_pfn     (itlb0_s_pfn     ),
    .tlb_v       (itlb0_s_v       ),
    .tlb_c       (itlb0_s_c       ),

    .tlb_vpn2    (itlb0_s_vpn2    ),
    .tlb_odd_page(itlb0_s_odd_page),
    
    .tlb_refill (instr_tlb_refill ),
    .tlb_invalid(instr_tlb_invalid)
  );

  tlb0 itlb0(
    .clk(clk),
    .rst(rst),

    // search port 0
    .s0_vpn2    (itlb0_s_vpn2    ),
    .s0_odd_page(itlb0_s_odd_page),  
    .s0_asid    (itlb0_s_asid    ),

    .s0_found   (itlb0_s_found   ),
    .s0_pfn     (itlb0_s_pfn     ),
    .s0_c       (itlb0_s_c       ),
    .s0_d       (itlb0_s_d       ),
    .s0_v       (itlb0_s_v       ),

    // write port
    .we     (itlb0_we     ),
    .w_vpn2 (itlb0_w_vpn2 ),
    .w_asid (itlb0_w_asid ),
    .w_g    (itlb0_w_g    ),
    .w_pfn0 (itlb0_w_pfn0 ),
    .w_c0   (itlb0_w_c0   ),
    .w_d0   (itlb0_w_d0   ),
    .w_v0   (itlb0_w_v0   ),
    .w_pfn1 (itlb0_w_pfn1 ),
    .w_c1   (itlb0_w_c1   ),
    .w_d1   (itlb0_w_d1   ),
    .w_v1   (itlb0_w_v1   )
  );

  dva2pa mycpu_dva2pa(
    .clk(clk),
    .rst(rst),
    .flush(dtlb0_flush),
    .stall(dtlb0_stall),

    .vaddr  (data_vaddr  ),
    .vvalid (data_vaddr_valid),

    .paddr  (data_paddr  ),
    .pvalid (data_paddr_valid),

    .cp0_config0(cp0_config0_rdata),
    .uncache    (data_uncache     ),

    .tlb_found   (dtlb0_s_found   ),
    .tlb_pfn     (dtlb0_s_pfn     ),
    .tlb_c       (dtlb0_s_c       ),
    .tlb_d       (dtlb0_s_d       ),
    .tlb_v       (dtlb0_s_v       ),

    .tlb_vpn2    (dtlb0_s_vpn2    ),
    .tlb_odd_page(dtlb0_s_odd_page),

    .is_store    (is_store         ),
    .tlb_refill  (data_tlb_refill  ),
    .tlb_invalid (data_tlb_invalid ),
    .tlb_modified(data_tlb_modified)
  );

  tlb0 dtlb0(
    .clk(clk),
    .rst(rst),

    // search port 0
    .s0_vpn2    (dtlb0_s_vpn2    ),
    .s0_odd_page(dtlb0_s_odd_page),  
    .s0_asid    (dtlb0_s_asid    ),

    .s0_found   (dtlb0_s_found   ),
    .s0_pfn     (dtlb0_s_pfn     ),
    .s0_c       (dtlb0_s_c       ),
    .s0_d       (dtlb0_s_d       ),
    .s0_v       (dtlb0_s_v       ),

    // write port
    .we     (dtlb0_we     ),
    .w_vpn2 (dtlb0_w_vpn2 ),
    .w_asid (dtlb0_w_asid ),
    .w_g    (dtlb0_w_g    ),
    .w_pfn0 (dtlb0_w_pfn0 ),
    .w_c0   (dtlb0_w_c0   ),
    .w_d0   (dtlb0_w_d0   ),
    .w_v0   (dtlb0_w_v0   ),
    .w_pfn1 (dtlb0_w_pfn1 ),
    .w_c1   (dtlb0_w_c1   ),
    .w_d1   (dtlb0_w_d1   ),
    .w_v1   (dtlb0_w_v1   )
  );

  tlb#(
    `TLB_NUM
  ) mycpu_tlb(
    .clk(clk),
    .rst(rst),
    // search port 0
    .s0_vpn2    (itlb0_s_vpn2),
    .s0_asid    (itlb0_s_asid),

    .s0_found   (s0_found   ),
    .s0_index   (s0_index   ),
    .s0_g       (s0_g  ),

    .s0_pfn0     (s0_pfn0),
    .s0_c0       (s0_c0  ),
    .s0_d0       (s0_d0  ),
    .s0_v0       (s0_v0  ),

    .s0_pfn1     (s0_pfn1),
    .s0_c1       (s0_c1  ),
    .s0_d1       (s0_d1  ),
    .s0_v1       (s0_v1  ),

    // search port 1
    .s1_vpn2    (dtlb0s_vpn2),
    .s1_asid    (dtlb0s_asid),

    .s1_found   (s1_found   ),
    .s1_index   (s1_index   ),
    .s1_g       (s1_g       ),
    
    .s1_pfn0     (s1_pfn0     ),
    .s1_c0       (s1_c0       ),
    .s1_d0       (s1_d0       ),
    .s1_v0       (s1_v0       ),

    .s1_pfn1     (s1_pfn1     ),
    .s1_c1       (s1_c1       ),
    .s1_d1       (s1_d1       ),
    .s1_v1       (s1_v1       ),

    // search port 2
    .s2_vpn2    (s2_vpn2 ),
    .s2_asid    (s2_asid ),
    .s2_found   (s2_found),
    .s2_index   (s2_index),

    // write port
    .we     (we     ),
    .w_index(w_index),
    .w_vpn2 (w_vpn2 ),
    .w_asid (w_asid ),
    .w_g    (w_g    ),
    .w_pfn0 (w_pfn0 ),
    .w_c0   (w_c0   ),
    .w_d0   (w_d0   ),
    .w_v0   (w_v0   ),
    .w_pfn1 (w_pfn1 ),
    .w_c1   (w_c1   ),
    .w_d1   (w_d1   ),
    .w_v1   (w_v1   ),
    
    // read port
    .r_index(r_index),
    .r_vpn2 (r_vpn2 ),
    .r_asid (r_asid ),
    .r_g    (r_g    ),
    .r_pfn0 (r_pfn0 ),
    .r_c0   (r_c0   ),
    .r_d0   (r_d0   ),
    .r_v0   (r_v0   ),
    .r_pfn1 (r_pfn1 ),
    .r_c1   (r_c1   ),
    .r_d1   (r_d1   ),
    .r_v1   (r_v1   )
  );

endmodule
